package model.searchers.advancedSearchers.hashTableSearchers;
import model.searchers.URLList;
import model.searchers.advancedSearchers.nestedLinkedListSearcher.NestedLinkedList;
import model.searchers.linkedListSearcher.LinkedList;

/**
 * Class StaticHashTable represents a static hash table, which is an array in which we store NestedLinkedLists. 
 * The NestedLinkedLists to store contains search words and the URLs on which the word occurs, together with 
 * the number of occurrences. It is stored on an index in the array computed by the hash function h(k) = |k mod n|, 
 * where n is size of hash table and k is hash code of key (search word).
 * computed from the hashcode of the key (search word). 
 * We can add new search words to the hash table, as well as we can add new URLs to the list of URLs on which the word occurs.
 * We can also retrieve URLList corresponding to a given search word from the hash table.
 */

public class StaticHashTable {
	protected NestedLinkedList index[]; //hash table
	private int wordCount[]; //array containing number of elements of the NestedLinkedList on each index in the index-array
	private int totalWordCount = 0; //total number of search words in the index-array
	private int place = 0;

	/**
	 * Constructor of StaticHashTable
	 * 
	 * @param size	size (length) of hash table
	 */
	public StaticHashTable(int size) {
		index = new NestedLinkedList[size];
		resetWordCount(size);
	}
	
	/**
	 * Add a pair of a key and a value to the hash table. In this search engine, the key will be a search word and the value will be a URL on which the word occurs.
	 * If the word is already there, the URL is added to URLList of the word.
	 * 
	 * @param key		key (search word) to be added
	 * @param value		value (URL) corresponding to the key
	 */
	public void put(String key, String value) {
		//computes where in the hash table to put the key from its hash code and a hash function
		place = Math.abs(key.hashCode()) % (index.length);
		NestedLinkedList current, start, temp;
		if (index[place] == null) {
			
			// First time allocating to that index
			index[place] = new NestedLinkedList(key, new URLList(value, null),
					null);
			incrementTotalWordCount();
			incrementWordCount(place);
		} else		{
			//check if the word is already in NestedLinkedList of the specific place in array
			start = index[place];
			current = start;
			NestedLinkedList existingWord = (NestedLinkedList) current.getFromList(key);
			if (existingWord == null) {
				// The word does not exist on place so we create a new NestedLinkedList with new word 
				// and URL added to front of old list. WordCounters are also updated
				incrementTotalWordCount();
				incrementWordCount(place);
				temp = new NestedLinkedList(key, new URLList(value, null),
						start);
				index[place] = temp;
			} else { 
				// The word does exist on place, so we need to check if the URL exist in corresponding URLList
				URLList tmpUrlList = existingWord.getUrl();
				if (tmpUrlList.getFromList(value) != null) {
					// The URL exists so we add 1 to the counter
					tmpUrlList.increaseOccurences();
				} else {
					// The URL does not exist so we create a new URLlist object
					// with the the new URL added in front of old list
					URLList tmpUrl = new URLList(value, existingWord.getUrl());
					existingWord.setUrl(tmpUrl);
				}
			}
		}
	}

	/**
	 * Look-up in the hash table.
	 * Find the URLs matching a given search word, that is the URLs on which the word occurs
	 * 
	 * @param key	search word
	 * @return	URLList containing the URLs on which search word occurs
	 */
	public URLList get(String key) {
		//computes where to find key from its hashcode and hash function
		int place = Math.abs(key.hashCode()) % (index.length);
		NestedLinkedList temp;
		if (index[place] != null) {
			temp = index[place];
			while (temp != null) {
				if (temp.getValue().equals(key)) {
					return temp.getUrl().clone();
				}
				temp = (NestedLinkedList) temp.getNext();
			}
		}
		return null;
	}

	/**
	 * @return size of hash table
	 */
	public int getLength() {
		return index.length;
	}

	/**
	 * Create a sample of specified size containing (pseudo)random search words from the hash table.
	 * To be used for benchmarking
	 * 
	 * @param sampleSize	size of sample
	 * @return	array of search words
	 */
	public String[] createSample(int sampleSize) {
		String[] sample = new String[sampleSize];
		int i = 0;

		while (i < sampleSize) {
			LinkedList temp = null;
			//find pseudorandom index in hash table
			int j = (int) (Math.random() * index.length);
			//find pseudorandom number from the number of words on that index of hash table
			int dataPoint = (int) (Math.random() * getWordCount(j));
		
			if (index[j] != null) 
			 temp = index[j].getFromList(dataPoint); //pseudorandom LinkedList from hash table

			//add word (key) of temp to sample
			if (temp != null && temp.getValue() != null) {
				sample[i] = temp.getValue();
				i++;
			}
		}
		return sample;
	}
	
	/**
	 * Increment count of words on a given index in the hash table
	 * 
	 * @param index	place in index-array
	 */
	public void incrementWordCount(int index) {
		if (0<=index && index<=wordCount.length)
			this.wordCount[index]++;
	}

	/**
	 * @param index place in index-array
	 * @return the number of words on a given index in the hash table
	 */
	public int getWordCount(int index) {
		if (0<=index && index<=this.index.length)
			return wordCount[index];
		else return 0;
	}
	
	/**
	 * Set wordCount-array to a new array of specified size
	 * 
	 * @param size	length of new wordCount-array
	 */
	public void resetWordCount(int size){
		if (size>0)
			this.wordCount = new int[size];
	}

	public int getTotalWordCount() {
		return totalWordCount;
	}
	
	public void incrementTotalWordCount() {
		totalWordCount++;
	}
	
	/**
	 * Set the counter of total number of words in hash table to 0
	 */
	//declared protected as it needs (only) to be called from subclass DynamicHashMap
	protected void resetTotalWordCount() {
		totalWordCount = 0;
	}

}
